/**
 * @file boards\gd32f103cb\bsp\bsp_mobus_t35.c
 *
 * Copyright (C) 2023
 *
 * bsp_mobus_t35.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "timer.h"
#include "gd32f10x_libopt.h"
#include "options.h"

/*---------- macro ----------*/
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static bool bsp_init(void);
static void bsp_deinit(void);
static bool bsp_enable(bool ctrl);

/*---------- variable ----------*/
static timer_describe_t t35 = {
    .freq = 1000,
    .ops.init = bsp_init,
    .ops.deinit = bsp_deinit,
    .ops.enable = bsp_enable,
    .ops.irq_handler = NULL
};
DEVICE_DEFINED(mobust35, timer, &t35);

/*---------- function ----------*/
static inline uint32_t _get_auto_reload(uint32_t clock_freq, uint32_t prescaler, uint32_t timer_freq)
{
    uint32_t auto_reload = 0;

    if((clock_freq / (prescaler + 1)) >= timer_freq) {
        auto_reload = (uint32_t)(((float)clock_freq / ((float)timer_freq * (prescaler + 1))) + 0.5) - 1;
    }

    return auto_reload;
}

static void bsp_timer_init(void)
{
    timer_parameter_struct timer = {0};
    uint32_t timer_clock_freq = rcu_clock_freq_get(CK_APB1);

    rcu_periph_clock_enable(RCU_TIMER1);
    if((RCU_CFG0 & RCU_CFG0_APB1PSC) != RCU_APB1_CKAHB_DIV1) {
        timer_clock_freq <<= 1;
    }
    timer_deinit(TIMER1);
    timer_struct_para_init(&timer);
    do {
        timer.period = _get_auto_reload(timer_clock_freq, timer.prescaler, t35.freq);
        if(timer.period < UINT16_MAX) {
            break;
        }
        timer.prescaler++;
    } while(true);
    timer_init(TIMER1, &timer);
    timer_single_pulse_mode_config(TIMER1, TIMER_SP_MODE_SINGLE);
    timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP);
    timer_update_event_enable(TIMER1);
    timer_interrupt_enable(TIMER1, TIMER_INT_UP);
}

static inline void bsp_timer_deinit(void)
{
    timer_disable(TIMER1);
    timer_interrupt_disable(TIMER1, TIMER_INT_UP);
    timer_update_event_disable(TIMER1);
    timer_deinit(TIMER1);
    rcu_periph_clock_disable(RCU_TIMER1);
}

static inline void bsp_nvic_init(void)
{
    NVIC_ClearPendingIRQ(TIMER1_IRQn);
    NVIC_SetPriority(TIMER1_IRQn, NVIC_PRIORITY_MAKE(CONFIG_PRIORITY_MOBUS_T35));
    NVIC_EnableIRQ(TIMER1_IRQn);
}

static inline void bsp_nvic_deinit(void)
{
    NVIC_DisableIRQ(TIMER1_IRQn);
    NVIC_ClearPendingIRQ(TIMER1_IRQn);
}

static bool bsp_init(void)
{
    bsp_timer_init();
    bsp_nvic_init();

    return true;
}

static void bsp_deinit(void)
{
    bsp_nvic_deinit();
    bsp_timer_deinit();
}

static bool bsp_enable(bool en)
{
    if(en) {
        timer_counter_value_config(TIMER1, 0);
        timer_enable(TIMER1);
    } else {
        timer_disable(TIMER1);
    }

    return true;
}
